﻿using Gargoyle.Roton;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Gargoyle.ZZT
{
    class Display
    {
        protected VideoSection DisplaySection;
        protected Game Game;
        protected Video Input;
        protected Video Output;
        protected bool SuspendGame;

        public Display(Video newInputVideo, Video newOutputVideo, Game newGame)
        {
            Game = newGame;
            Input = newInputVideo;
            Output = newOutputVideo;
            SuspendGame = false;
        }

        public bool Busy
        {
            get
            {
                return SuspendGame;
            }
        }

        public virtual void RedrawHud()
        {
        }

        public virtual void Update()
        {
        }

        public virtual void UpdateAt(Location location)
        {
        }

        public virtual void UpdateHud()
        {
        }
    }

    public enum SelectionParameter
    {
        BoardName,
        Cheat,
        GameSpeed,
        LoadBoard,
        LoadWorld,
        QuitGame,
        QuitZZT,
        SaveBoard,
        SaveWorld
    }

    class UtilityDisplay : Display
    {

        protected enum SelectionType
        {
            Standard,
            Vector,
            Character,
            Boolean,
            Name,
            String,
            YesNo,
            Scroll
        }

        protected enum ScrollStateEnum
        {
            None,
            Opening,
            Open,
            Closing,
            Closed
        }

        protected static readonly byte[] scrollCharsTop = {
            0xC6, 0xD1, 0xCD, 0xD1, 0xB5 };
        protected static readonly byte[] scrollCharsMid = {
            0x20, 0xB3, 0x20, 0xB3, 0x20 };
        protected static readonly byte[] scrollCharsSplit = {
            0x20, 0xC6, 0xCD, 0xB5, 0x20 };
        protected static readonly byte[] scrollCharsBottom = {
            0xC6, 0xCF, 0xCD, 0xCF, 0xB5 };

        public ByteString PromptString = new byte[] { };
        public ByteString ScrollLine = new byte[] { };
        public int SelectedChoice = -1;
        public ByteString SelectedString = new byte[] { };
        public SelectionParameter SelectParameter;

        protected bool AnimatingInternal = false;
        protected AnsiChar FadeChar;
        protected int FadeIndex = -1;
        protected int FadeRate;
        protected Rectangle FadeRect;
        protected bool FadingIn = false;
        protected int[] FadeMatrix;
        protected Rectangle GameRect;
        protected int LineIndex = -1;
        protected int ScrollContentLength;
        protected ByteString[] ScrollContents;
        protected int ScrollDistance;
        protected int ScrollLength;
        protected ByteString ScrollName;
        protected Rectangle ScrollRect;
        protected ScrollStateEnum ScrollState;
        protected bool SelectingInternal;

        protected const int MaxScrollLength = 0x7FFF;

        public UtilityDisplay(Video newInputVideo, Video newOutputVideo, Game newGame, Rectangle newFadeRect, int newFadeRate, AnsiChar newFadeChar, Rectangle newGameRect)
            : base(newInputVideo, newOutputVideo, newGame)
        {
            FadeChar = newFadeChar;
            FadeRate = newFadeRate;
            FadeRect = newFadeRect;
            FadeMatrix = GenerateFadeMatrix(FadeRect.Width * FadeRect.Height);
            GameRect = newGameRect;
        }

        public virtual bool Animating()
        {
            return AnimatingInternal;
        }

        public virtual void ClearMessage()
        {
        }

        public virtual void DrawMessage(ByteString message, byte color)
        {
        }

        protected void DrawScrollBorderLine(Point point, int width, byte[] scrollChars)
        {
            int left = point.X;
            int right = point.X + width - 1;
            int Y = point.Y;

            Output.SetChar(new Point(left, Y), new AnsiChar(scrollChars[0], 0x0F));
            Output.SetChar(new Point(left + 1, Y), new AnsiChar(scrollChars[1], 0x0F));
            Output.SetChar(new Point(right - 1, Y), new AnsiChar(scrollChars[3], 0x0F));
            Output.SetChar(new Point(right, Y), new AnsiChar(scrollChars[4], 0x0F));
            for (int i = left + 2; i < right - 1; i++)
            {
                Output.SetChar(new Point(i, point.Y), new AnsiChar(scrollChars[2], 0x0F));
            }
        }

        protected virtual void DrawStatusBar()
        {
        }

        protected virtual void EraseScrollLine(int y)
        {
        }

        public virtual void FadeIn()
        {
            SuspendGame = true;
            FadeIndex = 0;
            FadingIn = true;
        }

        public virtual void FadeOut()
        {
            SuspendGame = true;
            FadeIndex = 0;
            FadingIn = false;
        }

        protected int[] GenerateFadeMatrix(int values)
        {
            Random rand = new Random();
            int[] result = new int[values];

            for (int i = 0; i < values; i++)
                result[i] = i;

            for (int i = 0; i < values; i++)
            {
                int randomValue = rand.Next(values);
                int swapValue = result[randomValue];
                result[randomValue] = result[i];
                result[i] = swapValue;
            }

            return result;
        }

        protected virtual void GetDisplaySection()
        {
            DisplaySection = Input.GetSection(GameRect);
        }

        protected virtual void GetScrollSection()
        {
        }

        protected void ProcessFade()
        {
            AnsiChar ch = FadeChar;

            for (int i = 0; i < FadeRate; i++)
            {
                if (FadeIndex >= FadeMatrix.Length)
                {
                    FadingIn = false;
                    FadeIndex = -1;
                    SuspendGame = false;
                    break;
                }
                
                int matrixIndex = FadeMatrix[FadeIndex];
                int x = matrixIndex % DisplaySection.Width;
                int y = matrixIndex / DisplaySection.Width;

                if (FadingIn)
                {
                    UpdateAt(new Location((byte)x, (byte)y));
                }
                else
                {
                    Output.SetChar(new Point(x + FadeRect.X, y + FadeRect.Y), ch);
                }
                
                FadeIndex++;
            }

            AnimatingInternal = true;
        }

        public override void RedrawHud()
        {
            base.RedrawHud();

            AnimatingInternal = true;
            DrawStatusBar();
            switch (Game.State.PlayerElement.Id)
            {
                case ElementID.Player:
                    RedrawHudGame();
                    break;
                case ElementID.Monitor:
                    RedrawHudTitle();
                    break;
            }
            UpdateHud();
        }

        protected virtual void RedrawHudGame()
        {
        }

        protected virtual void RedrawHudTitle()
        {
        }

        protected virtual void SelectChoice()
        {
            SuspendGame = false;
            SelectingInternal = false;
        }

        protected virtual void SelectLine()
        {
            if (LineIndex < 0) return;
            string line = ScrollContents[LineIndex];
            bool jumpLabel = false;

            if (line.StartsWith("!") && line.Contains(";"))
            {
                string label = "";
                label = line.Substring(1);
                label = label.Substring(0, label.IndexOf(';')).ToUpperInvariant();
                ScrollLine = label;
                label = ":" + label + ";";
                for (int i = 0; i < ScrollContentLength; i++)
                {
                    line = ScrollContents[i];
                    if (line.ToUpperInvariant().StartsWith(label))
                    {
                        LineIndex = i;
                        jumpLabel = true;
                        break;
                    }
                }
            }
            else
            {
                if (SelectingInternal)
                {
                    ScrollLine = line;
                }
                else
                {
                    ScrollLine = new byte[] { };
                }
            }

            if (!jumpLabel)
            {
                ScrollState = ScrollStateEnum.Closing;
            }
        }

        public bool Selecting
        {
            get
            {
                return SelectingInternal;
            }
        }

        public virtual void ShowChoice(SelectionParameter newSelectParameter, int defaultValue)
        {
            SelectedChoice = defaultValue;
            SelectParameter = newSelectParameter;
            SuspendGame = true;
            SelectingInternal = true;
        }

        public virtual void ShowFileEntry(int maxLength)
        {
        }

        public virtual void ShowScroll(ByteString name, ByteString content)
        {
            SuspendGame = true;
            LineIndex = 0;
            ScrollContents = content.Split(0x0D);
            ScrollName = name.Clone();
            ScrollState = ScrollStateEnum.Opening;
            ScrollContentLength = ScrollContents.Length;
            ScrollDistance = ScrollRect.Height / 2;
            ScrollLength = content.Length;
        }

        public virtual void ShowStringEntry(int maxLength)
        {
        }

        public override void Update()
        {
            AnimatingInternal = false;

            if (ScrollState != ScrollStateEnum.None)
            {
                if (ScrollState == ScrollStateEnum.Closed)
                {
                    // cleanup scroll
                    EraseScrollLine(ScrollDistance);
                    ScrollState = ScrollStateEnum.None;
                    SuspendGame = false;
                    SelectingInternal = false;
                    AnimatingInternal = true;

                    // this is a bug in the original game engines
                    if (ScrollLength > MaxScrollLength)
                    {
                        throw new InvalidOperationException("Scroll length exceeds " + MaxScrollLength.ToString() + ". (This is a forced crash.)");
                    }
                }

                switch (ScrollState)
                {
                    case ScrollStateEnum.Opening:
                        ScrollDistance--;
                        UpdateScrollFrame(true);
                        if (ScrollDistance == 0)
                        {
                            LineIndex = -1;
                            ScrollState = ScrollStateEnum.Open;
                            UpdateScroll();
                        }
                        AnimatingInternal = true;
                        break;
                    case ScrollStateEnum.Open:
                        UpdateScroll();
                        break;
                    case ScrollStateEnum.Closing:
                        EraseScrollLine(ScrollDistance);
                        EraseScrollLine((ScrollRect.Height - 1) - ScrollDistance);
                        ScrollDistance++;
                        UpdateScrollFrame(false);
                        if (ScrollDistance == ScrollRect.Height / 2)
                        {
                            ScrollState = ScrollStateEnum.Closed;
                        }
                        AnimatingInternal = true;
                        break;
                }
            }
            else if (SelectingInternal)
            {
                UpdateSelection();
                if (!SelectingInternal)
                {
                    SuspendGame = false;
                }
            }
            else if (FadeIndex >= 0)
            {
                ProcessFade();
            }
            else
            {
                UpdateScreen();
            }
        }

        public override void UpdateAt(Location location)
        {
            base.UpdateAt(location);

            AnsiChar ch = DisplaySection.Chars[location.X + (location.Y * DisplaySection.Width)];
            Output.SetChar(new Point(FadeRect.X + location.X, FadeRect.Y + location.Y), ch);
        }

        public override void UpdateHud()
        {
            base.UpdateHud();

            switch (Game.State.PlayerElement.Id)
            {
                case ElementID.Player:
                    UpdateHudGame();
                    break;
                case ElementID.Monitor:
                    UpdateHudTitle();
                    break;
            }

            AnimatingInternal = true;
        }

        protected virtual void UpdateHudGame()
        {
        }

        protected virtual void UpdateHudTitle()
        {
        }

        protected virtual void UpdateScreen()
        {
        }

        protected virtual void UpdateSelection()
        {
        }

        protected virtual void UpdateScroll()
        {
            int oldIndex = LineIndex;
            Input input = Game.Input;
            Keys pressedKey = input.GetPressedKey();

            switch (pressedKey)
            {
                case Keys.Up:
                    LineIndex--;
                    break;
                case Keys.Down:
                    LineIndex++;
                    break;
                case Keys.PageUp:
                    LineIndex -= ScrollRect.Height - 5;
                    break;
                case Keys.PageDown:
                    LineIndex += ScrollRect.Height - 5;
                    break;
                case Keys.Escape:
                    ScrollLine = new byte[] { };
                    ScrollState = ScrollStateEnum.Closing;
                    break;
                case Keys.Enter:
                    SelectLine();
                    break;
            }

            if (LineIndex < 0)
                LineIndex = 0;
            if (LineIndex >= ScrollContentLength)
                LineIndex = ScrollContentLength - 1;
            if (LineIndex != oldIndex)
            {
                AnimatingInternal = true;
                UpdateScrollRedraw();
            }
        }

        protected virtual void UpdateScrollFrame(bool fillCenter)
        {
            int x = ScrollRect.Left;
            int y = ScrollRect.Top + ScrollDistance;
            int width = ScrollRect.Width;
            int height = ScrollRect.Height - (ScrollDistance * 2);

            if (fillCenter)
            {
                for (int i = 1; i < height - 1; i++)
                {
                    DrawScrollBorderLine(new Point(x, y + i), width, scrollCharsMid);
                }
            }
            DrawScrollBorderLine(new Point(x, y), width, scrollCharsTop);
            if (ScrollDistance == 0)
            {
                DrawScrollBorderLine(new Point(x, y + 2), width, scrollCharsSplit);
            }
            DrawScrollBorderLine(new Point(x, y + height - 1), width, scrollCharsBottom);
        }

        protected virtual void UpdateScrollRedraw()
        {
            int centerX = ScrollRect.Left + (ScrollRect.Width / 2);
            int x = ScrollRect.Left + 2;
            int y = ScrollRect.Top + 3;
            int width = ScrollRect.Width - 4;
            int height = ScrollRect.Height - 4;
            int midHeight = height / 2;
            int index = LineIndex - midHeight;
            string text;
            ByteString name = ScrollName;

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    Output.SetChar(new Point(x + j, y + i), new AnsiChar(0x20, 0x1E));
                }

                if (index == LineIndex)
                {
                    Output.SetChar(new Point(x, y + i), new AnsiChar(175, 0x1C));
                    Output.SetChar(new Point(x + width - 1, y + i), new AnsiChar(174, 0x1C));
                }

                if (index == -1 || index == ScrollContents.Length)
                {
                    text = null;
                    for (int j = 4; j < width; j += 5)
                    {
                        Output.SetChar(new Point(x + j, y + i), new AnsiChar(0x07, 0x1E));
                    }
                }
                else if (index >= 0 && index < ScrollContents.Length)
                    text = ScrollContents[index];
                else
                    text = null;

                if (text != null)
                {
                    if (text.Length > 0)
                    {
                        switch (text[0])
                        {
                            case '$':
                                Output.WriteString(new Point(centerX - ((text.Length - 1) / 2), y + i), text.Substring(1), 0x1F);
                                break;
                            case '!':
                                Output.SetChar(new Point(x + 4, y + i), new AnsiChar(0x10, 0x1D));
                                Output.WriteString(new Point(x + 6, y + i), text.Substring(text.IndexOf(';') + 1), 0x1F);
                                if (index == LineIndex)
                                    name = Game.Parameters.PressEnterMessage;
                                break;
                            case ':':
                                Output.WriteString(new Point(x + 2, y + i), text.Substring(text.IndexOf(';') + 1), 0x1F);
                                break;
                            default:
                                Output.WriteString(new Point(x + 2, y + i), text, 0x1E);
                                break;
                        }

                    }
                }
                index++;
            }

            for (int j = 0; j < width; j++)
            {
                Output.SetChar(new Point(x + j, y - 2), new AnsiChar(0x20, 0x1E));
            }
            Output.WriteString(new Point(centerX - ((name.Length - 1) / 2), y - 2), name, 0x1E);

        }
    }

    class ZZTDisplay : UtilityDisplay
    {
        private static AnsiChar StatusBarBackground = new AnsiChar(0, 0x1E);

        protected int Choice = 0;
        protected ByteString ChoiceString = "";
        protected SelectionType SelectType;
        protected int SelectY = 0;

        public ZZTDisplay(Video newInputVideo, Video newOutputVideo, Game newGame)
            : base(newInputVideo, newOutputVideo, newGame, new Rectangle(0, 0, 60, 25), 100, new AnsiChar(0x20, 0x50), new Rectangle(1, 1, 60, 25))
        {
            ScrollRect = new Rectangle(5, 3, 49, 19);
        }

        public override void ClearMessage()
        {
            base.ClearMessage();

            for (int i = 0; i < FadeRect.Width; i++)
            {
                UpdateAt(new Location((byte)(FadeRect.X + i), (byte)(FadeRect.Y)));
                UpdateAt(new Location((byte)(FadeRect.X + i), (byte)(FadeRect.Y + FadeRect.Height - 1)));
            }

            for (int i = 0; i < FadeRect.Height; i++)
            {
                UpdateAt(new Location((byte)(FadeRect.X), (byte)(FadeRect.Y + i)));
                UpdateAt(new Location((byte)(FadeRect.X + FadeRect.Width - 1), (byte)(FadeRect.Y + i)));
            }
        }

        public override void DrawMessage(ByteString message, byte color)
        {
            base.DrawMessage(message, color);
            int messageX = 30 - (message.Length / 2);

            Output.WriteString(new Point(messageX, 24), " " + message + " ", color);
        }

        protected override void DrawStatusBar()
        {
            for (int y = 0; y < 25; y++)
            {
                DrawStatusBarLine(y);
            }
        }

        private void DrawStatusBarLine(int y)
        {
            for (int x = 60; x < 80; x++)
                {
                    Output.SetChar(new Point(x, y), StatusBarBackground);
                }
        }

        protected override void EraseScrollLine(int y)
        {
            base.EraseScrollLine(y);
            Output.SetSection(new Point(ScrollRect.X, ScrollRect.Y + y), Input.GetSection(new Rectangle(ScrollRect.X + 1, ScrollRect.Y + y + 1, ScrollRect.Width, 1)));
        }

        public override void FadeIn()
        {
            base.FadeIn();
        }

        public override void FadeOut()
        {
            GetDisplaySection();
            base.FadeOut();
        }

        protected override void GetDisplaySection()
        {
            base.GetDisplaySection();
        }

        public override void RedrawHud()
        {
            base.RedrawHud();
        }

        protected override void RedrawHudGame()
        {
            Output.WriteString(new Point(65, 0), ("- - - - -"), 0x1F);
            Output.WriteString(new Point(62, 1), ("      ZZT      "), 0x70);
            Output.WriteString(new Point(65, 2), ("- - - - -"), 0x1F);
            Output.SetChar(new Point(62, 7), new AnsiChar(0x02, 0x1F));
            Output.WriteString(new Point(65, 7), ("Health:"), 0x1E);
            Output.SetChar(new Point(62, 8), new AnsiChar(0x84, 0x1B));
            Output.WriteString(new Point(67, 8), ("Ammo:"), 0x1E);
            Output.SetChar(new Point(62, 9), new AnsiChar(0x9D, 0x16));
            Output.WriteString(new Point(64, 9), ("Torches:"), 0x1E);
            Output.SetChar(new Point(62, 10), new AnsiChar(0x04, 0x1B));
            Output.WriteString(new Point(67, 10), ("Gems:"), 0x1E);
            Output.WriteString(new Point(66, 11), ("Score:"), 0x1E);
            Output.SetChar(new Point(62, 12), new AnsiChar(0x0C, 0x1F));
            Output.WriteString(new Point(67, 12), ("Keys:"), 0x1E);
            Output.WriteString(new Point(62, 14), (" T "), 0x70);
            Output.WriteString(new Point(66, 14), ("Torch"), 0x1F);
            Output.WriteString(new Point(62, 15), (" B "), 0x30);
            Output.WriteString(new Point(66, 15), ("Be "), 0x1F);
            Output.WriteString(new Point(62, 16), (" H "), 0x70);
            Output.WriteString(new Point(66, 16), ("Help"), 0x1F);
            Output.WriteString(new Point(67, 18), new byte[] { 0x20, 0x18, 0x19, 0x1A, 0x1B }, 0x30);
            Output.WriteString(new Point(73, 18), ("Move"), 0x1F);
            Output.WriteString(new Point(61, 19), (" Shift"), 0x70);
            Output.WriteString(new Point(67, 19), new byte[] { 0x20, 0x18, 0x19, 0x1A, 0x1B }, 0x70);
            Output.WriteString(new Point(73, 19), ("Shoot"), 0x1F);
            Output.WriteString(new Point(62, 21), (" S "), 0x70);
            Output.WriteString(new Point(66, 21), ("Save game"), 0x1F);
            Output.WriteString(new Point(62, 22), (" P "), 0x30);
            Output.WriteString(new Point(66, 22), ("Pause"), 0x1F);
            Output.WriteString(new Point(62, 23), (" Q "), 0x70);
            Output.WriteString(new Point(66, 23), ("Quit"), 0x1F);
        }

        protected override void RedrawHudTitle()
        {
            Output.WriteString(new Point(65, 0), ("- - - - -"), 0x1F);
            Output.WriteString(new Point(62, 1), ("      ZZT      "), 0x70);
            Output.WriteString(new Point(65, 2), ("- - - - -"), 0x1F);
            Output.WriteString(new Point(62, 5), ("Pick a command:"), 0x1B);
            Output.WriteString(new Point(62, 7), (" W "), 0x30);
            Output.WriteString(new Point(66, 7), ("World:"), 0x1E);
            Output.WriteString(new Point(62, 11), (" P "), 0x70);
            Output.WriteString(new Point(66, 11), ("Play"), 0x1F);
            Output.WriteString(new Point(62, 12), (" R "), 0x30);
            Output.WriteString(new Point(66, 12), ("Restore game"), 0x1E);
            Output.WriteString(new Point(62, 13), (" Q "), 0x70);
            Output.WriteString(new Point(66, 13), ("Quit"), 0x1E);
            Output.WriteString(new Point(62, 16), (" A "), 0x30);
            Output.WriteString(new Point(66, 16), ("About ZZT!"), 0x1F);
            Output.WriteString(new Point(62, 17), (" H "), 0x70);
            Output.WriteString(new Point(66, 17), ("High Scores"), 0x1E);
            Output.WriteString(new Point(62, 21), (" S "), 0x70);
            Output.WriteString(new Point(66, 21), ("Game speed:"), 0x1E);
            Output.WriteString(new Point(66, 23), ("F....:....S"), 0x1E);
        }

        public override void ShowChoice(SelectionParameter selectParameter, int defaultValue)
        {
            base.ShowChoice(selectParameter, defaultValue);
            Choice = -1;
            ChoiceString = "";
            switch (selectParameter)
            {
                case SelectionParameter.Cheat:
                    SelectType = SelectionType.String;
                    break;
                case SelectionParameter.GameSpeed:
                    SelectType = SelectionType.Standard;
                    SelectY = 22;
                    break;
                case SelectionParameter.QuitGame:
                case SelectionParameter.QuitZZT:
                    SelectType = SelectionType.YesNo;
                    SelectY = 5;
                    break;
                case SelectionParameter.LoadBoard:
                case SelectionParameter.LoadWorld:
                    SelectType = SelectionType.Scroll;
                    break;
            }

            switch (SelectType)
            {
                case SelectionType.YesNo:
                    DrawStatusBarLine(SelectY);
                    Output.WriteString(new Point(63, SelectY), PromptString, 0x1F);
                    Output.WriteString(new Point(63 + PromptString.Length + 1, SelectY), "_", 0x9F);
                    break;
                case SelectionType.String:
                    SelectY = 4;
                    Output.WriteString(new Point(63, SelectY + 1), "           ", 0x0F);
                    break;
            }
            this.SelectingInternal = true;
            Game.Input.ClearPressedKey();
        }

        public override void ShowScroll(ByteString name, ByteString content)
        {
            base.ShowScroll(name, content);
        }

        public override void Update()
        {
            base.Update();
        }

        protected override void UpdateHudGame()
        {
            Info info = Game.Info;
            Board board = Game.CurrentBoard;
            Parameters parameters = Game.Parameters;
            State state = Game.State;

            if (board.TimeLimit > 0)
                Output.WriteString(new Point(67, 6), ("Time:" + (board.TimeLimit - info.TimePassed).ToString() + " "), 0x1E);
            else
                DrawStatusBarLine(6);

            // never display negative health values
            string displayHealth = info.Health < 0 ? "0" : info.Health.ToString();
            Output.WriteString(new Point(72, 7), (displayHealth + " "), 0x1E);
            Output.WriteString(new Point(72, 8), (info.Ammo.ToString() + " "), 0x1E);
            Output.WriteString(new Point(72, 9), (info.Torches.ToString() + " "), 0x1E);
            Output.WriteString(new Point(72, 10), (info.Gems.ToString() + " "), 0x1E);
            Output.WriteString(new Point(72, 11), (info.Score.ToString() + " "), 0x1E);

            for (int i = 0; i < parameters.KeyCount; i++)
            {
                AnsiChar keyChar;
                if (info.Keys[i] != 0)
                    keyChar = new AnsiChar(0x0C, (byte)(0x19 + i));
                else
                    keyChar = new AnsiChar(0x20, 0x1E);
                Output.SetChar(new Point(72 + i, 12), keyChar);
            }

            string soundString;
            if (state.Quiet)
                soundString = "Noisy";
            else
                soundString = "Quiet";
            Output.WriteString(new Point(69, 15), (soundString), 0x1F);

            if (state.Paused)
                Output.WriteString(new Point(64, 5), "Pausing...", 0x1F);
        }

        protected override void UpdateHudTitle()
        {
            Info info = Game.Info;
            State state = Game.State;

            Output.WriteString(new Point(69, 8), info.Name, 0x1F);
            Output.SetChar(new Point(67 + state.Speed, 22), new AnsiChar(0x1F, 0x1F));
        }

        protected override void UpdateScreen()
        {
            GetDisplaySection();
            Output.SetSection(new Point(0, 0), DisplaySection);
        }

        protected override void UpdateSelection()
        {
            Input input = Game.Input;
            Keys pressedKey = input.GetPressedKey();
            int selectX = 4;
            int lastSelection = Choice;
            ByteString lastString = ChoiceString;

            if (Choice == -1)
                Choice = SelectedChoice;

            // process input per type
            switch (SelectType)
            {
                case SelectionType.Boolean:
                case SelectionType.Character:
                case SelectionType.Standard:
                case SelectionType.Vector:
                    switch (pressedKey)
                    {
                        case Keys.Left:
                            Choice--;
                            break;
                        case Keys.Right:
                            Choice++;
                            break;
                        case Keys.Enter:
                            SelectedChoice = Choice;
                            SelectingInternal = false;
                            AnimatingInternal = false;
                            break;
                        case Keys.Escape:
                            SelectingInternal = false;
                            break;
                    }
                    break;
                case SelectionType.Name:
                    break;
                case SelectionType.String:
                    if (pressedKey != Keys.None)
                        AnimatingInternal = true;
                    else
                        AnimatingInternal = false;
                    if (ChoiceString.Length < 11)
                    {
                        if ((int)pressedKey >= 0x20 && (int)pressedKey < 0x80)
                        {
                            ChoiceString.Append((byte)pressedKey);
                        }
                        else if (pressedKey == Keys.Oemplus)
                        {
                            ChoiceString.Append(0x2B); // +
                        }
                        else if (pressedKey == Keys.OemMinus)
                        {
                            if (input.Shift)
                                ChoiceString.Append(0x5F); // _
                            else
                                ChoiceString.Append(0x2D); // -
                        }
                    }
                    if (pressedKey == Keys.Escape)
                    {
                        SelectedString = "";
                        SelectingInternal = false;
                        DrawStatusBarLine(SelectY);
                        DrawStatusBarLine(SelectY + 1);
                        break;
                    }
                    if (pressedKey == Keys.Enter)
                    {
                        SelectedString = ChoiceString;
                        SelectingInternal = false;
                        DrawStatusBarLine(SelectY);
                        DrawStatusBarLine(SelectY + 1);
                        break;
                    }
                    if ((pressedKey == Keys.Back) && (ChoiceString.Length > 0))
                    {
                        ChoiceString.Resize(ChoiceString.Length - 1);
                    }
                    break;
                case SelectionType.YesNo:
                    switch (pressedKey)
                    {
                        case Keys.N:
                            SelectedChoice = 0;
                            SelectingInternal = false;
                            AnimatingInternal = false;
                            break;
                        case Keys.Y:
                            SelectedChoice = 1;
                            SelectingInternal = false;
                            AnimatingInternal = false;
                            break;
                        case Keys.Escape:
                            SelectedChoice = -1;
                            SelectingInternal = false;
                            AnimatingInternal = false;
                            break;
                    }
                    break;
            }

            // process selection
            switch (SelectType)
            {
                case SelectionType.Standard:
                    if (Choice < 0)
                        Choice = 0;
                    if (Choice > 8)
                        Choice = 8;
                    selectX = Choice;
                    break;
                case SelectionType.Vector:
                    if (Choice < 0)
                        Choice = 0;
                    if (Choice > 4)
                        Choice = 4;
                    selectX = Choice * 2;
                    break;
                case SelectionType.Character:
                    if (Choice < 0)
                        Choice = 255;
                    if (Choice > 255)
                        Choice = 0;
                    selectX = 4;
                    if (Choice != lastSelection)
                    {
                        for (int i = 0; i < 9; i++)
                        {
                            Output.SetChar(new Point(64 + i, SelectY + 1), new AnsiChar((byte)((i + Choice - 4) & 0xFF), 0x1E));
                        }
                    }
                    break;
                case SelectionType.Boolean:
                    if (Choice < 127)
                        Choice = 0;
                    if (Choice > 127)
                        Choice = 128;
                    selectX = Choice / 16;
                    break;
            }

            // process screen update
            if (SelectingInternal)
            {
                switch (SelectType)
                {
                    case SelectionType.Boolean:
                    case SelectionType.Character:
                    case SelectionType.Standard:
                    case SelectionType.Vector:
                        if (Choice != lastSelection)
                        {
                            DrawStatusBarLine(SelectY);
                            Output.SetChar(new Point(67 + selectX, SelectY), new AnsiChar(0x1F, 0x1F));
                        }
                        break;
                    case SelectionType.Name:
                        break;
                    case SelectionType.String:
                        DrawStatusBarLine(SelectY);
                        Output.SetChar(new Point(63 + ChoiceString.Length, SelectY), new AnsiChar(0x1F, 0x1F));
                        Output.WriteString(new Point(63, SelectY + 1), "           ", 0x0F);
                        Output.WriteString(new Point(63, SelectY + 1), ChoiceString, 0x0F);
                        break;
                    case SelectionType.YesNo:
                        break;
                }
            }
        }
    }

    class SuperZZTDisplay : UtilityDisplay
    {
        protected int Choice = 0;
        protected SelectionType SelectType;
        protected int SelectY = 0;

        private Point camera;
        private Rectangle scrollDeadzone;
        private VideoSection[] scrollSection;

        public SuperZZTDisplay(Video newInputVideo, Video newOutputVideo, Game newGame)
            : base(newInputVideo, newOutputVideo, newGame, new Rectangle(14, 2, 24, 20), 20, new AnsiChar(0x20, 0x50), new Rectangle(1, 1, 24, 20))
        {
            ScrollRect = new Rectangle(1, 2, 37, 23);
            scrollDeadzone = new Rectangle(-1, -3, 5, 6);
        }

        public override void ClearMessage()
        {
            base.ClearMessage();

            AnsiChar backChar = new AnsiChar(0x20, 0x1E);
            for (int x = 12; x < 40; x++)
                for (int y = 23; y < 25; y++)
                    Output.SetChar(new Point(x, y), backChar);
        }

        private void DrawHudNumber(int y, Int16 value)
        {
            ByteString number = value.ToString();
            Output.WriteString(new Point(11 - number.Length, y), number, 0x6E);
        }

        public override void DrawMessage(ByteString message, byte color)
        {
            base.DrawMessage(message, color);
            ByteString[] messages = message.Split(0x0D);
            int messageY = 25 - messages.Length;

            for (int i = 0; i < messages.Length; i++)
            {
                int messageX = 27 - (messages[i].Length / 2);
                Output.WriteString(new Point(messageX, messageY), messages[i], (byte)(color | 0x10));
                messageY++;
            }

        }

        protected override void DrawStatusBar()
        {
            base.DrawStatusBar();

            AnsiChar boxTop = new AnsiChar(220, 0x1F);
            AnsiChar boxBottom = new AnsiChar(223, 0x7F);
            AnsiChar boxBottomLeft = new AnsiChar(223, 0x1F);
            AnsiChar boxWall = new AnsiChar(219, 0x1F);
            AnsiChar boxShadow = new AnsiChar(221, 0x17);
            AnsiChar backChar = new AnsiChar(0x20, 0x1F);

            for (int x = 12; x < 40; x++)
            {
                Output.SetChar(new Point(x, 0), backChar);
                Output.SetChar(new Point(x, 23), backChar);
                Output.SetChar(new Point(x, 24), backChar);
            }

            for (int y = 1; y < 3; y++)
            {
                Output.SetChar(new Point(12, y), backChar);
                Output.SetChar(new Point(39, y), backChar);
            }

            for (int y = 3; y < 23; y++)
            {
                Output.SetChar(new Point(12, y), backChar);
            }

            for (int x = 13; x <= 38; x++)
            {
                Output.SetChar(new Point(x, 1), boxTop);
                if (x == 13)
                    Output.SetChar(new Point(x, 22), boxBottomLeft);
                else
                    Output.SetChar(new Point(x, 22), boxBottom);
            }

            for (int y = 2; y <= 21; y++)
            {
                Output.SetChar(new Point(13, y), boxWall);
                Output.SetChar(new Point(38, y), boxWall);
                Output.SetChar(new Point(39, y + 1), boxShadow);
            }
        }

        protected override void EraseScrollLine(int y)
        {
            base.EraseScrollLine(y);
            Output.SetSection(new Point(ScrollRect.X, y + ScrollRect.Y), scrollSection[y]);
        }

        public override void FadeIn()
        {
            base.FadeIn();
        }

        public override void FadeOut()
        {
            GetDisplaySection();
            base.FadeOut();
        }

        protected override void GetDisplaySection()
        {
            base.GetDisplaySection();
        }

        protected override void GetScrollSection()
        {
            base.GetScrollSection();
        }

        public override void RedrawHud()
        {
            base.RedrawHud();
        }

        protected override void RedrawHudGame()
        {
            base.RedrawHudGame();

            AnsiChar headerTop = new AnsiChar(220, 0x1D);
            AnsiChar headerBottom = new AnsiChar(223, 0x6D);
            AnsiChar backChar = new AnsiChar(0x20, 0x6E);

            for (int x = 0; x < 12; x++)
                for (int y = 1; y < 24; y++)
                    Output.SetChar(new Point(x, y), backChar);

            for (int x = 0; x < 12; x++)
            {
                Output.SetChar(new Point(x, 0), headerTop);
                Output.SetChar(new Point(x, 2), headerBottom);
                Output.SetChar(new Point(x, 12), headerTop);
                Output.SetChar(new Point(x, 14), headerBottom);
            }

            Output.WriteString(new Point(2, 1), "Commands", 0x6F);
            Output.WriteString(new Point(1, 3), new byte[] {0x18, 0x19, 0x1A, 0x1B}, 0x6F);
            Output.WriteString(new Point(3, 4), "Move", 0x6E);
            Output.WriteString(new Point(1, 5), "Shift+", 0x6F);
            Output.WriteString(new Point(7, 5), new byte[] { 0x18, 0x19, 0x1A, 0x1B }, 0x6F);
            Output.WriteString(new Point(3, 6), "Shoot", 0x6B);
            Output.WriteString(new Point(1, 7), "H", 0x6F);
            Output.WriteString(new Point(3, 7), "Hint", 0x6E);
            Output.WriteString(new Point(1, 8), "S", 0x6F);
            Output.WriteString(new Point(3, 8), "Save Game", 0x6B);
            Output.WriteString(new Point(1, 9), "R", 0x6F);
            Output.WriteString(new Point(3, 9), "Restore", 0x6E);
            Output.WriteString(new Point(1, 10), "B", 0x6F);
            Output.WriteString(new Point(3, 10), "Be", 0x6E);
            Output.WriteString(new Point(1, 11), "Q", 0x6F);
            Output.WriteString(new Point(3, 11), "Quit", 0x6E);

            Output.WriteString(new Point(3, 13), "Status", 0x6F);
            Output.WriteString(new Point(0, 15), "Health", 0x6F);
            Output.WriteString(new Point(1, 17), "Gems", 0x6F);
            Output.SetChar(new Point(6, 17), new AnsiChar(0x04, 0x62));
            Output.WriteString(new Point(1, 18), "Ammo", 0x6F);
            Output.SetChar(new Point(6, 18), new AnsiChar(132, 0x6B));
            Output.WriteString(new Point(1, 19), "Keys", 0x6F);
            Output.WriteString(new Point(1, 21), "Score", 0x6F);
        }

        protected override void RedrawHudTitle()
        {
            base.RedrawHudTitle();

            AnsiChar backChar = new AnsiChar(0x20, 0x1F);
            for (int x = 0; x < 12; x++)
                for (int y = 0; y < 25; y++)
                    Output.SetChar(new Point(x, y), backChar);

            Output.WriteString(new Point(4, 10), "Press", 0x1E);
            Output.WriteString(new Point(4, 12), "ENTER", 0x1F);
            Output.WriteString(new Point(1, 14), "to continue", 0x1E);
        }

        protected override void SelectLine()
        {
            base.SelectLine();
        }

        public override void ShowChoice(SelectionParameter selectParameter, int defaultValue)
        {
            base.ShowChoice(selectParameter, defaultValue);
            Choice = -1;
            switch (selectParameter)
            {
                case SelectionParameter.Cheat:
                    SelectType = SelectionType.String;
                    break;
                case SelectionParameter.QuitGame:
                    SelectType = SelectionType.YesNo;
                    break;
                case SelectionParameter.QuitZZT:
                    SelectType = SelectionType.YesNo;
                    break;
            }
        }

        public override void ShowFileEntry(int maxLength)
        {
            base.ShowFileEntry(maxLength);
        }

        public override void ShowScroll(ByteString name, ByteString content)
        {
            base.ShowScroll(name, content);
            scrollSection = new VideoSection[ScrollRect.Height];
            for (int i = 0; i < ScrollRect.Height; i++)
            {
                scrollSection[i] = Output.GetSection(new Rectangle(ScrollRect.X, i + ScrollRect.Y, ScrollRect.Width, 1));
            }
        }

        public override void ShowStringEntry(int maxLength)
        {
            base.ShowStringEntry(maxLength);
        }

        public override void Update()
        {
            if (FadeIndex == 0 && FadingIn)
            {
                Thing player = Game.Things[0];
                camera = player.Location.ToPoint();
                UpdateCamera();
                GetDisplaySection();
            }
            base.Update();
        }

        public override void UpdateAt(Location location)
        {
            base.UpdateAt(location);
        }

        private void UpdateCamera()
        {
            if (Game.Things != null && Game.Things[0] != null)
            {
                Thing player = Game.Things[0];
                Point playerPoint = player.Location.ToPoint();
                Rectangle playerDeadzone = scrollDeadzone;
                playerDeadzone.Offset(playerPoint);

                if (Math.Abs(camera.X - player.X) > ScrollRect.Width / 2)
                    camera.X = player.X;

                if (Math.Abs(camera.Y - player.Y) > ScrollRect.Height / 2)
                    camera.Y = player.Y;


                if (camera.X < playerDeadzone.Left)
                {
                    camera.X = playerDeadzone.Left;
                }
                if (camera.X >= playerDeadzone.Right)
                {
                    camera.X = playerDeadzone.Right - 1;
                }
                if (camera.Y < playerDeadzone.Top)
                {
                    camera.Y = playerDeadzone.Top;
                }
                if (camera.Y >= playerDeadzone.Bottom)
                {
                    camera.Y = playerDeadzone.Bottom - 1;
                }

                Size inputSize = Input.GetSize();
                Point finalCamera = camera;
                finalCamera.X -= (GameRect.Width / 2);
                finalCamera.Y -= (GameRect.Height / 2);
                if (finalCamera.X <= 0)
                    finalCamera.X = 1;
                if (finalCamera.X >= inputSize.Width - GameRect.Width)
                    finalCamera.X = (inputSize.Width - GameRect.Width) - 1;
                if (finalCamera.Y <= 0)
                    finalCamera.Y = 1;
                if (finalCamera.Y >= inputSize.Height - GameRect.Height)
                    finalCamera.Y = (inputSize.Height - GameRect.Height) - 1;
                GameRect.Location = finalCamera;
            }
        }

        public override void UpdateHud()
        {
            base.UpdateHud();
        }

        protected override void UpdateHudGame()
        {
            base.UpdateHudGame();
            Info info = Game.Info;
            State state = Game.State;
            AnsiChar backChar = new AnsiChar(0x20, 0x6E);

            string soundString;
            if (state.Quiet)
                soundString = "Noisy";
            else
                soundString = "Quiet";
            Output.WriteString(new Point(6, 10), (soundString), 0x6E);

            {
                int health = info.Health;
                for (int i = 7; i <= 11; i++)
                {
                    if (health >= 20)
                    {
                        Output.SetChar(new Point(i, 15), new AnsiChar(0xDB, 0x6E));
                        health -= 20;
                    }
                    else if (health >= 10)
                    {
                        Output.SetChar(new Point(i, 15), new AnsiChar(0xDD, 0x6E));
                        health -= 10;
                    }
                    else
                    {
                        Output.SetChar(new Point(i, 15), new AnsiChar(0x20, 0x6E));
                    }
                }
            }

            for (int i = 0; i < info.Keys.Length; i++)
            {
                AnsiChar keyChar;
                if (info.Keys[i] != 0)
                    keyChar = new AnsiChar(0x0C, (byte)(0x60 | (i + 9)));
                else
                    keyChar = new AnsiChar(0x20, 0x6E);

                Output.SetChar(new Point(7 + (i % 4), 19 + (i / 4)), keyChar);
            }

            DrawHudNumber(17, info.Gems);
            DrawHudNumber(18, info.Ammo);
            DrawHudNumber(21, info.Score);

            for (int i = 1; i <= 10; i++)
            {
                Output.SetChar(new Point(i, 22), backChar);
            }

            for (int i = 0; i < info.Flags.Length; i++)
            {
                if (info.Flags[i].Length > 0 && info.Flags[i][0] == 0x5A)  // Z
                {
                    ByteString stoneString = info.Flags[i].Clone();
                    stoneString[0] = 0x20;
                    Output.WriteString(new Point(0, 22), stoneString, 0x6F);
                }
            }

            if (info.Stones >= 0)
                DrawHudNumber(22, info.Stones);

            if (Game.State.Paused)
                Output.WriteString(new Point(21, 24), "Pausing...", 0x1E);
        }

        protected override void UpdateHudTitle()
        {
            base.UpdateHudTitle();
        }

        protected override void UpdateScreen()
        {
            UpdateCamera();
            GetDisplaySection();
            Output.SetSection(new Point(14, 2), DisplaySection);
        }

        protected override void UpdateSelection()
        {
            base.UpdateSelection();
        }
    }
}
